import sys
import os
import pandas as pd
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.font_manager as fm
import logging
import gc
import weakref
import time
import re
from datetime import datetime, timedelta
from matplotlib import dates as mdates
from PyQt5.QtWidgets import (
    QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
    QLabel, QLineEdit, QPushButton, QComboBox, QTextEdit,
    QProgressBar, QFrame, QSplitter, QMessageBox, QListWidget,
    QListWidgetItem, QAction, QMenu, QTabWidget, QToolBar,
    QToolButton, QInputDialog, QDialog, QDialogButtonBox,
    QSizePolicy, QSpacerItem, QFontDialog, QGridLayout
)
from PyQt5.QtCore import Qt, QSize, QTimer, QEvent
from PyQt5.QtGui import QFont, QIcon, QColor, QPixmap, QRegularExpressionValidator
from PyQt5.QtCore import QRegularExpression


# 自定义异常事件类
class QExceptionEvent(QEvent):
    EVENT_TYPE = QEvent.Type(QEvent.registerEventType())

    def __init__(self, message, traceback=""):
        super().__init__(QExceptionEvent.EVENT_TYPE)
        self.message = message
        self.traceback = traceback


# 导入自定义模块
from modules.stock_cache_manager import StockCacheManager
from modules.stock_processor import StockProcessor
from modules.cache_clean_dialog import CacheCleanDialog
from modules.mpl_canvas import MplCanvas
from modules.fuzzy_search import FuzzySearch  # 导入模糊搜索模块
from modules.chan_state_dialog import ChanStateDialog

# 配置日志
logger = logging.getLogger(__name__)


class ThemeManager:
    """主题管理器类"""

    def __init__(self):
        self.current_theme = "light"  # 默认浅色主题

    def apply_theme(self, theme_name):
        """应用主题到整个应用程序"""
        self.current_theme = theme_name

        app = QApplication.instance()
        if not app:
            return

        if theme_name == "light":
            self._apply_light_theme(app)
        elif theme_name == "dark":
            self._apply_dark_theme(app)
        elif theme_name == "blue":
            self._apply_blue_theme(app)
        elif theme_name == "green":
            self._apply_green_theme(app)

    def _apply_light_theme(self, app):
        """浅色主题"""
        app.setStyle("Fusion")
        palette = app.palette()
        palette.setColor(palette.Window, QColor(240, 240, 240))
        palette.setColor(palette.WindowText, QColor(0, 0, 0))
        palette.setColor(palette.Base, QColor(255, 255, 255))
        palette.setColor(palette.AlternateBase, QColor(230, 230, 230))
        palette.setColor(palette.ToolTipBase, QColor(255, 255, 220))
        palette.setColor(palette.ToolTipText, QColor(0, 0, 0))
        palette.setColor(palette.Text, QColor(0, 0, 0))
        palette.setColor(palette.Button, QColor(240, 240, 240))
        palette.setColor(palette.ButtonText, QColor(0, 0, 0))
        palette.setColor(palette.BrightText, QColor(255, 0, 0))
        palette.setColor(palette.Highlight, QColor(50, 150, 255))
        palette.setColor(palette.HighlightedText, QColor(255, 255, 255))
        app.setPalette(palette)
        app.setStyleSheet("""
            QToolTip { 
                color: #000000; 
                background-color: #FFFFDC; 
                border: 1px solid #AAAAAA; 
            }
            QTabWidget::pane { border: 0; }
        """)

    def _apply_dark_theme(self, app):
        """深色主题"""
        app.setStyle("Fusion")
        palette = app.palette()
        palette.setColor(palette.Window, QColor(53, 53, 53))
        palette.setColor(palette.WindowText, QColor(255, 255, 255))
        palette.setColor(palette.Base, QColor(35, 35, 35))
        palette.setColor(palette.AlternateBase, QColor(53, 53, 53))
        palette.setColor(palette.ToolTipBase, QColor(25, 25, 25))
        palette.setColor(palette.ToolTipText, QColor(255, 255, 255))
        palette.setColor(palette.Text, QColor(255, 255, 255))
        palette.setColor(palette.Button, QColor(53, 53, 53))
        palette.setColor(palette.ButtonText, QColor(255, 255, 255))
        palette.setColor(palette.BrightText, QColor(255, 0, 0))
        palette.setColor(palette.Highlight, QColor(42, 130, 218))
        palette.setColor(palette.HighlightedText, QColor(255, 255, 255))
        app.setPalette(palette)
        app.setStyleSheet("""
            QToolTip { 
                color: #FFFFFF; 
                background-color: #2A2A2A; 
                border: 1px solid #444444; 
            }
            QTabWidget::pane { border: 0; }
            QTabBar::tab:selected { 
                background: #3A3A3A; 
                color: white; 
            }
        """)

    def _apply_blue_theme(self, app):
        """蓝色主题"""
        app.setStyle("Fusion")
        palette = app.palette()
        palette.setColor(palette.Window, QColor(240, 245, 255))
        palette.setColor(palette.WindowText, QColor(0, 30, 80))
        palette.setColor(palette.Base, QColor(255, 255, 255))
        palette.setColor(palette.AlternateBase, QColor(230, 238, 255))
        palette.setColor(palette.ToolTipBase, QColor(220, 230, 255))
        palette.setColor(palette.ToolTipText, QColor(0, 30, 80))
        palette.setColor(palette.Text, QColor(0, 30, 80))
        palette.setColor(palette.Button, QColor(200, 215, 255))
        palette.setColor(palette.ButtonText, QColor(0, 30, 80))
        palette.setColor(palette.BrightText, QColor(255, 0, 0))
        palette.setColor(palette.Highlight, QColor(70, 130, 200))
        palette.setColor(palette.HighlightedText, QColor(255, 255, 255))
        app.setPalette(palette)
        app.setStyleSheet("""
            QToolTip { 
                color: #001E50; 
                background-color: #DCE6FF; 
                border: 1px solid #A0B0FF; 
            }
            QPushButton {
                background-color: #C8D7FF;
                border: 1px solid #A0B0FF;
                border-radius: 4px;
                padding: 5px;
            }
            QPushButton:hover {
                background-color: #B0C5FF;
            }
            QTabWidget::pane { border: 0; }
        """)

    def _apply_green_theme(self, app):
        """绿色主题"""
        app.setStyle("Fusion")
        palette = app.palette()
        palette.setColor(palette.Window, QColor(240, 255, 245))
        palette.setColor(palette.WindowText, QColor(0, 80, 30))
        palette.setColor(palette.Base, QColor(255, 255, 255))
        palette.setColor(palette.AlternateBase, QColor(230, 255, 238))
        palette.setColor(palette.ToolTipBase, QColor(220, 255, 230))
        palette.setColor(palette.ToolTipText, QColor(0, 80, 30))
        palette.setColor(palette.Text, QColor(0, 80, 30))
        palette.setColor(palette.Button, QColor(200, 255, 215))
        palette.setColor(palette.ButtonText, QColor(0, 80, 30))
        palette.setColor(palette.BrightText, QColor(255, 0, 0))
        palette.setColor(palette.Highlight, QColor(70, 200, 130))
        palette.setColor(palette.HighlightedText, QColor(255, 255, 255))
        app.setPalette(palette)
        app.setStyleSheet("""
            QToolTip { 
                color: #00501E; 
                background-color: #DCFFE6; 
                border: 1px solid #A0FFB0; 
            }
            QPushButton {
                background-color: #C8FFD7;
                border: 1px solid #A0FFB0;
                border-radius: 4px;
                padding: 5px;
            }
            QPushButton:hover {
                background-color: #B0FFC5;
            }
            QTabWidget::pane { border: 0; }
        """)


class StockAnalysisApp(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("缠论K线分析工具 (AkShare版)")
        # 增大窗口初始尺寸
        self.setGeometry(100, 100, 1600, 1000)
        self.setWindowIcon(QIcon("resources/stock_icon.png"))
        self.current_stock_id = None
        self.current_data = None  # 存储当前股票数据
        self.current_key_points = []  # 存储当前关键点
        self.chan_state_data = {}  # 存储当前缠论状态数据
        self.alive = True  # 应用存活标志
        self.stock_name = ""  # 当前股票名称

        # 安全调用机制
        self.MAX_STACK_DEPTH = 100  # 最大递归深度限制
        self.current_stack_depth = 0  # 当前递归深度

        # 初始化主题管理器
        self.theme_manager = ThemeManager()
        self.global_font = QFont("Arial", 10)  # 默认全局字体
        self.chart_font_size = 10  # 默认图表字体大小

        # 设置中文字体支持 - 简化版
        self.setup_chinese_font()

        # 初始化缓存管理器
        self.cache_manager = StockCacheManager()

        # 初始化模糊搜索
        self.fuzzy_search = FuzzySearch()

        # 资源跟踪
        self._resource_refs = []

        # 主控件
        self.main_widget = QWidget()
        self.setCentralWidget(self.main_widget)

        # 主布局
        self.main_layout = QVBoxLayout(self.main_widget)

        # 创建工具栏 - 移动到右侧
        self.create_toolbar()

        # 创建顶部控制面板
        self.create_control_panel()

        # 创建分割视图
        self.splitter = QSplitter(Qt.Horizontal)
        self.main_layout.addWidget(self.splitter)

        # 左侧：图表区域 - 现在平分空间
        self.chart_container = QWidget()
        self.chart_layout = QVBoxLayout(self.chart_container)
        self.splitter.addWidget(self.chart_container)

        # 右侧：信息区域
        self.info_container = QWidget()
        self.info_layout = QVBoxLayout(self.info_container)
        self.splitter.addWidget(self.info_container)

        # 初始化图表 - 添加标题区域
        self.init_charts()

        # 初始化信息区域
        self.init_info_area()

        # 设置初始分割比例 - 左侧更大
        self.splitter.setSizes([1000, 400])

        # 状态栏
        self.status_bar = self.statusBar()
        self.status_bar.showMessage("就绪 | 使用AkShare数据源")

        # 处理器线程
        self.processor = StockProcessor(self.cache_manager)
        self._resource_refs.append(weakref.ref(self.processor))
        self.processor.data_fetched.connect(self.on_data_fetched)
        self.processor.processing_started.connect(self.on_processing_started)
        self.processor.processing_finished.connect(self.on_processing_finished)
        self.processor.key_points_ready.connect(self.on_key_points_ready)
        self.processor.error_occurred.connect(self.on_error_occurred)
        self.processor.cache_updated.connect(self.on_cache_updated)

        # 异步加载历史记录和自选股
        self.load_history_async()
        self.load_watchlist_async()

        # 创建菜单
        self.create_menus()

        # 创建搜索建议列表
        self.search_suggestions = QListWidget()
        self.search_suggestions.setVisible(False)
        self.search_suggestions.setMaximumHeight(150)
        self.search_suggestions.itemClicked.connect(self.select_search_suggestion)

        # 添加到主布局
        self.main_layout.insertWidget(1, self.search_suggestions)  # 放在控制面板下方

    def setup_chinese_font(self):
        """设置中文字体支持 - 增强版"""
        try:
            # 尝试添加常见的中文字体路径
            font_paths = [
                "C:/Windows/Fonts/simhei.ttf",  # Windows 黑体
                "C:/Windows/Fonts/simkai.ttf",  # Windows 楷体
                "C:/Windows/Fonts/simsun.ttc",  # Windows 宋体
                "C:/Windows/Fonts/msyh.ttc",  # Windows 微软雅黑
                "/System/Library/Fonts/PingFang.ttc",  # macOS 苹方
                "/usr/share/fonts/truetype/droid/DroidSansFallbackFull.ttf"  # Linux
            ]

            for path in font_paths:
                if os.path.exists(path):
                    # 使用matplotlib的字体管理器添加字体
                    font_prop = fm.FontProperties(fname=path)
                    # 添加到字体库
                    fm.fontManager.addfont(path)
                    logger.info(f"添加字体: {path}")

            # 设置默认字体
            plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei', 'SimSun', 'KaiTi', 'PingFang SC', 'STHeiti',
                                               'sans-serif']
            plt.rcParams['axes.unicode_minus'] = False

            # 添加字体缓存刷新
            if hasattr(fm, '_rebuild'):
                fm._rebuild()
            elif hasattr(fm, 'fontManager') and hasattr(fm.fontManager, '_rebuild'):
                fm.fontManager._rebuild()

            logger.info("中文字体设置完成")
        except Exception as e:
            logger.error(f"设置中文字体时出错: {str(e)}")
            # 回退方案
            plt.rcParams['font.sans-serif'] = ['Arial Unicode MS', 'sans-serif']
            plt.rcParams['axes.unicode_minus'] = False

    def safe_call(self, func, *args, **kwargs):
        """安全调用函数，防止堆栈溢出"""
        self.current_stack_depth += 1
        if self.current_stack_depth > self.MAX_STACK_DEPTH:
            logger.error(f"达到最大递归深度 {self.MAX_STACK_DEPTH}，终止调用: {func.__name__}")
            self.current_stack_depth = 0
            return None

        try:
            return func(*args, **kwargs)
        finally:
            self.current_stack_depth -= 1

    def create_toolbar(self):
        """创建工具栏 - 移动到右侧"""
        toolbar = QToolBar("主工具栏")
        toolbar.setIconSize(QSize(24, 24))
        # 改为右侧停靠
        self.addToolBar(Qt.RightToolBarArea, toolbar)

        # === 布局设置按钮 ===
        self.layout_menu = QMenu(self)
        self._resource_refs.append(weakref.ref(self.layout_menu))

        # 全局字体设置
        global_font_action = QAction("全局字体设置", self)
        global_font_action.setToolTip("设置应用程序全局字体")
        global_font_action.triggered.connect(self.set_global_font)
        self.layout_menu.addAction(global_font_action)

        # K线字体设置
        chart_font_action = QAction("K线字体设置", self)
        chart_font_action.setToolTip("设置K线图表中的字体大小")
        chart_font_action.triggered.connect(self.set_chart_font)
        self.layout_menu.addAction(chart_font_action)

        # 主题设置
        theme_menu = QMenu("更换主题", self)
        self._resource_refs.append(weakref.ref(theme_menu))

        # 主题选项
        light_theme_action = QAction("浅色主题", self)
        light_theme_action.triggered.connect(lambda: self.apply_theme("light"))
        theme_menu.addAction(light_theme_action)

        dark_theme_action = QAction("深色主题", self)
        dark_theme_action.triggered.connect(lambda: self.apply_theme("dark"))
        theme_menu.addAction(dark_theme_action)

        blue_theme_action = QAction("蓝色主题", self)
        blue_theme_action.triggered.connect(lambda: self.apply_theme("blue"))
        theme_menu.addAction(blue_theme_action)

        green_theme_action = QAction("绿色主题", self)
        green_theme_action.triggered.connect(lambda: self.apply_theme("green"))
        theme_menu.addAction(green_theme_action)

        self.layout_menu.addMenu(theme_menu)

        # 布局按钮
        layout_button = QToolButton()
        layout_button.setText("布局设置")
        layout_button.setIcon(QIcon("resources/layout.png"))
        layout_button.setMenu(self.layout_menu)
        layout_button.setPopupMode(QToolButton.InstantPopup)
        layout_button.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        toolbar.addWidget(layout_button)
        # === 新增结束 ===

        # 添加到自选股按钮
        self.btn_add_watchlist = QToolButton()
        self.btn_add_watchlist.setIcon(QIcon("resources/star.png"))
        self.btn_add_watchlist.setText("添加到自选")
        self.btn_add_watchlist.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        self.btn_add_watchlist.setToolTip("将当前股票添加到自选股")
        self.btn_add_watchlist.clicked.connect(self.add_current_to_watchlist)
        toolbar.addWidget(self.btn_add_watchlist)

        # 刷新按钮
        btn_refresh = QToolButton()
        btn_refresh.setIcon(QIcon("resources/refresh.png"))
        btn_refresh.setText("刷新")
        btn_refresh.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        btn_refresh.setToolTip("刷新当前视图")
        btn_refresh.clicked.connect(self.refresh_current)
        toolbar.addWidget(btn_refresh)

        # 管理自选股按钮
        btn_manage_watchlist = QToolButton()
        btn_manage_watchlist.setIcon(QIcon("resources/manage.png"))
        btn_manage_watchlist.setText("管理自选股")
        btn_manage_watchlist.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        btn_manage_watchlist.setToolTip("管理自选股列表")
        btn_manage_watchlist.clicked.connect(self.manage_watchlist)
        toolbar.addWidget(btn_manage_watchlist)

    def apply_theme(self, theme_name):
        """应用主题到应用程序"""
        self.theme_manager.apply_theme(theme_name)
        self.status_bar.showMessage(f"已应用 {theme_name} 主题", 3000)

        # 重新绘制图表
        if self.current_data is not None:
            self.safe_call(self.on_period_changed)

    def set_global_font(self):
        """设置全局字体"""
        font, ok = QFontDialog.getFont(self.global_font, self, "选择全局字体")
        if ok:
            self.global_font = font
            QApplication.instance().setFont(font)
            self.status_bar.showMessage(f"全局字体已设置为: {font.family()}, {font.pointSize()}pt", 3000)

    def set_chart_font(self):
        """设置K线图表字体大小"""
        sizes = [8, 9, 10, 11, 12, 14, 16, 18, 20, 22, 24]
        size, ok = QInputDialog.getItem(
            self,
            "K线字体设置",
            "选择图表字体大小:",
            [str(s) for s in sizes],
            current=sizes.index(self.chart_font_size) if self.chart_font_size in sizes else 2,
            editable=False
        )

        if ok and size:
            self.chart_font_size = int(size)
            self.status_bar.showMessage(f"图表字体大小已设置为: {size}pt", 3000)

            # 重新绘制图表
            if self.current_data is not None:
                self.safe_call(self.on_period_changed)

    def create_menus(self):
        """创建菜单栏"""
        menubar = self.menuBar()

        # 文件菜单
        file_menu = menubar.addMenu("文件")

        clear_cache_action = QAction("清理缓存", self)
        clear_cache_action.triggered.connect(self.clear_cache)
        file_menu.addAction(clear_cache_action)

        exit_action = QAction("退出", self)
        exit_action.setShortcut("Ctrl+Q")
        exit_action.triggered.connect(self.close)
        file_menu.addAction(exit_action)

        # 查看菜单
        view_menu = menubar.addMenu("查看")

        history_action = QAction("历史记录", self)
        history_action.setShortcut("Ctrl+H")
        history_action.triggered.connect(self.show_history)
        view_menu.addAction(history_action)

        popular_action = QAction("热门股票", self)
        popular_action.setShortcut("Ctrl+P")
        popular_action.triggered.connect(self.show_popular)
        view_menu.addAction(popular_action)

        watchlist_action = QAction("自选股", self)
        watchlist_action.setShortcut("Ctrl+W")
        watchlist_action.triggered.connect(self.show_watchlist)
        view_menu.addAction(watchlist_action)

        # 帮助菜单
        help_menu = menubar.addMenu("帮助")

        about_action = QAction("关于", self)
        about_action.triggered.connect(self.show_about)
        help_menu.addAction(about_action)

    def create_control_panel(self):
        """创建控制面板"""
        control_frame = QFrame()
        control_frame.setFrameShape(QFrame.StyledPanel)
        control_frame.setStyleSheet("background-color: #f0f0f0; padding: 5px;")
        control_layout = QHBoxLayout(control_frame)

        # 股票代码输入
        lbl_stock = QLabel("股票代码/名称:")
        self.entry_stock = QLineEdit()
        self.entry_stock.setPlaceholderText("输入股票代码或名称 (如: 600519, 腾讯, AAPL)")
        self.entry_stock.setMinimumWidth(300)
        self.entry_stock.returnPressed.connect(self.start_analysis)

        # 添加输入验证器 - 只允许字母、数字、中文和空格
        validator = QRegularExpressionValidator(
            QRegularExpression("[\u4e00-\u9fa5a-zA-Z0-9 ]*"),
            self.entry_stock
        )
        self.entry_stock.setValidator(validator)

        # 连接文本变化信号
        self.entry_stock.textChanged.connect(self.update_search_suggestions)

        # 历史按钮
        self.btn_history = QPushButton("历史")
        self.btn_history.setToolTip("查看历史记录")
        self.btn_history.setFixedWidth(60)
        self.btn_history.clicked.connect(self.show_history)

        # 周期选择
        lbl_period = QLabel("分析周期:")
        self.combo_period = QComboBox()
        self.combo_period.addItems(["1月", "3月", "6月", "1年", "2年", "5年"])
        self.combo_period.setCurrentIndex(3)  # 默认1年
        # 添加周期选择变化事件
        self.combo_period.currentIndexChanged.connect(self.on_period_changed)

        # 间隔选择 (主要支持日线)
        lbl_interval = QLabel("K线间隔:")
        self.combo_interval = QComboBox()
        self.combo_interval.addItems(["日线", "周线"])
        self.combo_interval.setCurrentIndex(0)  # 默认日线

        # 分析按钮
        self.btn_analyze = QPushButton("开始分析")
        self.btn_analyze.setStyleSheet("background-color: #4CAF50; color: white; font-weight: bold;")
        self.btn_analyze.clicked.connect(self.start_analysis)

        # 添加到布局
        control_layout.addWidget(lbl_stock)
        control_layout.addWidget(self.entry_stock)
        control_layout.addWidget(self.btn_history)
        control_layout.addWidget(lbl_period)
        control_layout.addWidget(self.combo_period)
        control_layout.addWidget(lbl_interval)
        control_layout.addWidget(self.combo_interval)
        control_layout.addWidget(self.btn_analyze)

        # 进度条
        self.progress_bar = QProgressBar()
        self.progress_bar.setVisible(False)

        self.main_layout.addWidget(control_frame)
        self.main_layout.addWidget(self.progress_bar)

    def init_charts(self):
        """初始化图表区域 - 添加标题并平分空间"""
        # 创建分割器使两个图表平分空间
        chart_splitter = QSplitter(Qt.Vertical)
        self.chart_layout.addWidget(chart_splitter)

        # 原始K线图区域
        original_frame = QWidget()
        original_layout = QVBoxLayout(original_frame)

        # 添加标题区域
        self.lbl_original_title = QLabel("原始K线图")
        self.lbl_original_title.setFont(QFont("Arial", 12, QFont.Bold))  # 增大字体
        self.lbl_original_title.setAlignment(Qt.AlignCenter)
        self.lbl_original_title.setStyleSheet("background-color: #e0e0e0; padding: 8px;")  # 增加内边距
        original_layout.addWidget(self.lbl_original_title)

        # 图表区域 - 使用动态尺寸
        self.canvas_original = MplCanvas(self, width=10, height=6, dpi=100)
        # 设置尺寸策略为扩展，让图表可以放大
        self.canvas_original.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        original_layout.addWidget(self.canvas_original)
        self._resource_refs.append(weakref.ref(self.canvas_original))

        # 添加到分割器
        chart_splitter.addWidget(original_frame)

        # 缠论分析图区域
        chan_frame = QWidget()
        chan_layout = QVBoxLayout(chan_frame)

        # 添加标题区域
        self.lbl_chan_title = QLabel("缠论分析结果")
        self.lbl_chan_title.setFont(QFont("Arial", 12, QFont.Bold))  # 增大字体
        self.lbl_chan_title.setAlignment(Qt.AlignCenter)
        self.lbl_chan_title.setStyleSheet("background-color: #e0e0e0; padding: 8px;")  # 增加内边距
        chan_layout.addWidget(self.lbl_chan_title)

        # 图表区域 - 使用动态尺寸
        self.canvas_chan = MplCanvas(self, width=10, height=6, dpi=100)
        # 设置尺寸策略为扩展，让图表可以放大
        self.canvas_chan.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        chan_layout.addWidget(self.canvas_chan)
        self._resource_refs.append(weakref.ref(self.canvas_chan))

        # 添加到分割器
        chart_splitter.addWidget(chan_frame)

        # 设置两个图表平分空间
        chart_splitter.setSizes([500, 500])

    def init_info_area(self):
        """初始化信息区域"""
        # 创建选项卡
        self.tab_widget = QTabWidget()
        self.info_layout.addWidget(self.tab_widget)
        self._resource_refs.append(weakref.ref(self.tab_widget))

        # 缠论状态分析 - 新添加的选项卡
        chan_state_frame = QWidget()
        chan_state_layout = QVBoxLayout(chan_state_frame)

        lbl_chan_state = QLabel("缠论状态分析")
        lbl_chan_state.setFont(QFont("Arial", 10, QFont.Bold))
        lbl_chan_state.setAlignment(Qt.AlignCenter)
        chan_state_layout.addWidget(lbl_chan_state)

        # 缠论状态信息容器
        self.chan_state_container = QWidget()
        self.chan_state_layout = QVBoxLayout(self.chan_state_container)
        chan_state_layout.addWidget(self.chan_state_container)

        # 初始状态文本
        self.lbl_chan_state_info = QLabel("请先分析股票以获取缠论状态")
        self.lbl_chan_state_info.setAlignment(Qt.AlignCenter)
        self.lbl_chan_state_info.setStyleSheet("color: gray; font-style: italic;")
        self.chan_state_layout.addWidget(self.lbl_chan_state_info)

        # 添加到选项卡
        self.tab_widget.addTab(chan_state_frame, "缠论状态")

        # 关键点分析
        key_points_frame = QWidget()
        key_points_layout = QVBoxLayout(key_points_frame)

        lbl_key_points = QLabel("关键点分析")
        lbl_key_points.setFont(QFont("Arial", 10, QFont.Bold))
        lbl_key_points.setAlignment(Qt.AlignCenter)
        key_points_layout.addWidget(lbl_key_points)

        self.txt_key_points = QTextEdit()
        self.txt_key_points.setReadOnly(True)
        self.txt_key_points.setFont(QFont("Consolas", 10))
        key_points_layout.addWidget(self.txt_key_points)

        # 添加到选项卡
        self.tab_widget.addTab(key_points_frame, "关键点分析")

        # 历史记录
        history_frame = QWidget()
        history_layout = QVBoxLayout(history_frame)

        lbl_history = QLabel("历史记录")
        lbl_history.setFont(QFont("Arial", 10, QFont.Bold))
        lbl_history.setAlignment(Qt.AlignCenter)
        history_layout.addWidget(lbl_history)

        self.list_history = QListWidget()
        self.list_history.itemDoubleClicked.connect(self.load_from_history)
        history_layout.addWidget(self.list_history)

        # 添加到选项卡
        self.tab_widget.addTab(history_frame, "历史记录")

        # 自选股
        watchlist_frame = QWidget()
        watchlist_layout = QVBoxLayout(watchlist_frame)

        lbl_watchlist = QLabel("自选股")
        lbl_watchlist.setFont(QFont("Arial", 10, QFont.Bold))
        lbl_watchlist.setAlignment(Qt.AlignCenter)
        watchlist_layout.addWidget(lbl_watchlist)

        self.list_watchlist = QListWidget()
        self.list_watchlist.itemClicked.connect(self.load_from_watchlist)
        self.list_watchlist.itemDoubleClicked.connect(self.remove_from_watchlist)
        watchlist_layout.addWidget(self.list_watchlist)

        # 添加到选项卡
        self.tab_widget.addTab(watchlist_frame, "自选股")

    def update_chan_state(self, state_data):
        """更新缠论状态显示"""
        # 清除原有内容
        for i in reversed(range(self.chan_state_layout.count())):
            widget = self.chan_state_layout.itemAt(i).widget()
            if widget:
                widget.deleteLater()

        # 创建状态表格
        grid_layout = QGridLayout()

        # 添加状态信息
        grid_layout.addWidget(QLabel("<b>当前分型:</b>"), 0, 0)
        grid_layout.addWidget(QLabel(state_data.get("current_fractal", "未知")), 0, 1)

        grid_layout.addWidget(QLabel("<b>当前笔:</b>"), 1, 0)
        grid_layout.addWidget(QLabel(state_data.get("current_pen", "未知")), 1, 1)

        grid_layout.addWidget(QLabel("<b>当前中枢:</b>"), 2, 0)
        grid_layout.addWidget(QLabel(state_data.get("current_zs", "未知")), 2, 1)

        grid_layout.addWidget(QLabel("<b>中枢最高价:</b>"), 3, 0)
        grid_layout.addWidget(QLabel(f"{state_data.get('zs_high', 0):.2f}"), 3, 1)

        grid_layout.addWidget(QLabel("<b>中枢最低价:</b>"), 4, 0)
        grid_layout.addWidget(QLabel(f"{state_data.get('zs_low', 0):.2f}"), 4, 1)

        # 添加详细分析
        details_label = QLabel("<b>详细分析:</b>")
        details_label.setContentsMargins(0, 15, 0, 0)  # 上边距
        grid_layout.addWidget(details_label, 5, 0, 1, 2)

        details_text = QTextEdit()
        details_text.setReadOnly(True)
        details_text.setPlainText(state_data.get("details", "没有详细信息"))
        details_text.setFixedHeight(100)
        grid_layout.addWidget(details_text, 6, 0, 1, 2)

        # 添加布局到容器
        self.chan_state_layout.addLayout(grid_layout)

    def update_search_suggestions(self, text):
        """更新搜索建议列表"""
        self.safe_call(self._update_search_suggestions, text)

    def _update_search_suggestions(self, text):
        """实际更新搜索建议列表"""
        try:
            # 如果文本为空或全是空格，隐藏建议列表
            if not text.strip():
                self.search_suggestions.setVisible(False)
                return

            # 执行模糊搜索
            results = self.fuzzy_search.search(text.strip())

            if not results:
                self.search_suggestions.setVisible(False)
                return

            # 更新建议列表
            self.search_suggestions.clear()
            for symbol, name, market in results:
                item = QListWidgetItem(f"{name} ({symbol}) [{market}]")
                item.setData(Qt.UserRole, symbol)  # 存储股票代码
                self.search_suggestions.addItem(item)

            self.search_suggestions.setVisible(True)
        except Exception as e:
            logger.error(f"更新搜索建议时出错: {str(e)}")
            self.search_suggestions.setVisible(False)

    def select_search_suggestion(self, item):
        """选择搜索建议"""
        symbol = item.data(Qt.UserRole)
        self.entry_stock.setText(symbol)
        self.search_suggestions.setVisible(False)
        self.start_analysis()  # 自动开始分析

    def load_history_async(self):
        """异步加载历史记录"""
        if not self.alive:
            return

        self.cache_manager.get_recent_stocks_async(
            15,
            self.on_history_loaded
        )
        self.status_bar.showMessage("正在加载历史记录...")

    def on_history_loaded(self, recent_stocks):
        """当历史记录加载完成时的回调"""
        if not self.alive:
            return

        self.list_history.clear()
        for stock in recent_stocks:
            stock_id, user_input, mapped_code, stock_name = stock
            item = QListWidgetItem(f"{stock_name} ({user_input})")
            item.setData(Qt.UserRole, (stock_id, user_input, mapped_code, stock_name))
            self.list_history.addItem(item)
        self.status_bar.showMessage("历史记录加载完成")

    def load_watchlist_async(self):
        """异步加载自选股列表"""
        if not self.alive:
            return

        self.cache_manager.get_watchlist_async(self.on_watchlist_loaded)
        self.status_bar.showMessage("正在加载自选股...")

    def on_watchlist_loaded(self, watchlist):
        """当自选股加载完成时的回调"""
        if not self.alive:
            return

        self.list_watchlist.clear()
        for stock in watchlist:
            stock_id, user_input, mapped_code, stock_name = stock
            item = QListWidgetItem(f"{stock_name} ({user_input})")
            item.setData(Qt.UserRole, (stock_id, user_input, mapped_code, stock_name))
            item.setIcon(QIcon("resources/delete.png"))
            self.list_watchlist.addItem(item)
        self.status_bar.showMessage("自选股加载完成")

    def show_history(self):
        """显示历史记录窗口"""
        self.tab_widget.setCurrentIndex(2)  # 更新索引

    def show_watchlist(self):
        """显示自选股窗口"""
        self.tab_widget.setCurrentIndex(3)  # 更新索引

    def show_popular(self):
        """显示热门股票窗口"""
        if not self.alive:
            return

        self.cache_manager.get_most_popular_stocks_async(
            15,
            self.on_popular_loaded
        )
        self.status_bar.showMessage("正在加载热门股票...")

    def on_popular_loaded(self, popular_stocks):
        """当热门股票加载完成时的回调"""
        if not self.alive:
            return

        self.list_history.clear()
        for stock in popular_stocks:
            stock_id, user_input, mapped_code, stock_name = stock
            item = QListWidgetItem(
                f"{stock_name} ({user_input}) - 访问次数: {self.cache_manager.get_access_count(stock_id)}")
            item.setData(Qt.UserRole, (stock_id, user_input, mapped_code, stock_name))
            self.list_history.addItem(item)

        self.tab_widget.setCurrentIndex(2)  # 历史记录选项卡
        self.status_bar.showMessage("热门股票加载完成")

    def load_from_history(self, item):
        """从历史记录加载股票"""
        stock_id, user_input, mapped_code, stock_name = item.data(Qt.UserRole)
        self.entry_stock.setText(user_input)
        self.start_analysis()

    def load_from_watchlist(self, item):
        """从自选股加载股票"""
        stock_id, user_input, mapped_code, stock_name = item.data(Qt.UserRole)
        self.entry_stock.setText(user_input)
        self.start_analysis()

    def remove_from_watchlist(self, item):
        """从自选股中移除股票"""
        stock_id, user_input, mapped_code, stock_name = item.data(Qt.UserRole)

        # 确认对话框
        reply = QMessageBox.question(self, '确认移除',
                                     f"确定要从自选股中移除 {stock_name} ({user_input}) 吗?",
                                     QMessageBox.Yes | QMessageBox.No,
                                     QMessageBox.No)

        if reply == QMessageBox.Yes:
            # 从数据库移除
            self.cache_manager.remove_from_watchlist(stock_id)

            # 更新列表
            self.load_watchlist_async()

            # 更新按钮状态
            self.update_watchlist_button()

    def add_current_to_watchlist(self):
        """将当前股票添加到自选股"""
        if self.current_stock_id is None:
            QMessageBox.warning(self, "操作失败", "请先选择一只股票进行分析")
            return

        # 添加到自选股
        if self.cache_manager.add_to_watchlist(self.current_stock_id):
            QMessageBox.information(self, "添加成功", "股票已添加到自选股")
            self.load_watchlist_async()
            self.update_watchlist_button()
        else:
            QMessageBox.information(self, "添加失败", "该股票已在自选股中")

    def update_watchlist_button(self):
        """更新添加到自选股按钮状态"""
        if self.current_stock_id is None:
            self.btn_add_watchlist.setEnabled(False)
            self.btn_add_watchlist.setText("添加到自选")
            return

        # 检查是否已在自选股中
        in_watchlist = self.cache_manager.is_in_watchlist(self.current_stock_id)

        self.btn_add_watchlist.setEnabled(not in_watchlist)

        if in_watchlist:
            self.btn_add_watchlist.setText("已在自选")
        else:
            self.btn_add_watchlist.setText("添加到自选")

    def manage_watchlist(self):
        """管理自选股对话框"""
        dialog = QDialog(self)
        dialog.setWindowTitle("管理自选股")
        dialog.resize(500, 400)

        layout = QVBoxLayout(dialog)

        # 自选股列表
        lbl_title = QLabel("我的自选股")
        lbl_title.setFont(QFont("Arial", 12, QFont.Bold))
        layout.addWidget(lbl_title)

        list_watchlist = QListWidget()
        layout.addWidget(list_watchlist)

        # 加载自选股
        watchlist = self.cache_manager.get_watchlist()
        for stock in watchlist:
            stock_id, user_input, mapped_code, stock_name = stock
            item = QListWidgetItem(f"{stock_name} ({user_input})")
            item.setData(Qt.UserRole, stock_id)
            list_watchlist.addItem(item)

        # 按钮区域
        btn_layout = QHBoxLayout()

        btn_remove = QPushButton("移除选中")
        btn_remove.clicked.connect(lambda: self.remove_selected_from_watchlist(list_watchlist))
        btn_layout.addWidget(btn_remove)

        btn_add = QPushButton("添加股票")
        btn_add.clicked.connect(lambda: self.add_stock_to_watchlist(dialog))
        btn_layout.addWidget(btn_add)

        btn_close = QPushButton("关闭")
        btn_close.clicked.connect(dialog.accept)
        btn_layout.addWidget(btn_close)

        layout.addLayout(btn_layout)

        dialog.exec_()

    def remove_selected_from_watchlist(self, list_widget):
        """移除选中的自选股"""
        selected_items = list_widget.selectedItems()
        if not selected_items:
            return

        for item in selected_items:
            stock_id = item.data(Qt.UserRole)
            self.cache_manager.remove_from_watchlist(stock_id)

        # 重新加载列表
        list_widget.clear()
        watchlist = self.cache_manager.get_watchlist()
        for stock in watchlist:
            stock_id, user_input, mapped_code, stock_name = stock
            item = QListWidgetItem(f"{stock_name} ({user_input})")
            item.setData(Qt.UserRole, stock_id)
            list_widget.addItem(item)

    def add_stock_to_watchlist(self, dialog):
        """添加股票到自选股"""
        stock_input, ok = QInputDialog.getText(
            dialog, "添加股票", "请输入股票代码或名称:"
        )

        if ok and stock_input:
            # 映射股票代码
            processor = StockProcessor(self.cache_manager)
            processor.user_input = stock_input
            symbol, name = processor.map_stock_code(stock_input)

            # 获取股票ID
            stock_id = self.cache_manager.get_stock_id(stock_input, symbol, name)

            # 添加到自选股
            if self.cache_manager.add_to_watchlist(stock_id):
                QMessageBox.information(dialog, "添加成功", f"{name} 已添加到自选股")

                # 重新加载列表
                self.load_watchlist_async()

                # 关闭对话框
                dialog.accept()
            else:
                QMessageBox.warning(dialog, "添加失败", "该股票已在自选股中")

    def clear_cache(self):
        """清理缓存"""
        dialog = CacheCleanDialog(self)
        if dialog.exec_() == QDialog.Accepted:
            settings = dialog.get_settings()

            # 清理旧缓存
            if settings['clean_older']:
                cleaned_count = self.cache_manager.clear_cache(settings['days'])
                QMessageBox.information(self, "缓存清理",
                                        f"已清理 {cleaned_count} 条超过 {settings['days']} 天的缓存记录")

            # 清理未使用的缓存
            if settings['clean_unused']:
                # 这里简化处理，实际应用中需要实现清理未使用缓存的功能
                QMessageBox.information(self, "缓存清理", f"未使用缓存清理功能将在下一版本实现")

            # 重新加载历史记录
            self.load_history_async()
            self.load_watchlist_async()

    def show_about(self):
        """显示关于对话框"""
        about_text = f"""
        <h2>缠论K线分析工具 (AkShare版)</h2>
        <p>版本: 4.0.1</p>
        <p>基于缠论原理的K线分析工具，使用AkShare作为数据源。</p>
        <p>主要功能:</p>
        <ul>
            <li>股票数据获取与缓存</li>
            <li>缠论K线处理与分析</li>
            <li>关键点识别与标记</li>
            <li>自选股管理</li>
            <li>历史记录与热门股票</li>
            <li>模糊搜索股票</li>
            <li>缠论状态分析</li>
        </ul>
        <p>注意事项:</p>
        <ul>
            <li>数据源来自AkShare，完全免费</li>
            <li>支持A股、港股、美股和主要指数</li>
            <li>日线数据使用前复权</li>
        </ul>
        <p>© 2025 缠论分析工具开发团队</p>
        """

        QMessageBox.about(self, "关于缠论K线分析工具", about_text)

    def start_analysis(self):
        """开始分析股票数据"""
        # 获取用户输入
        stock_input = self.entry_stock.text().strip()
        if not stock_input:
            QMessageBox.warning(self, "输入错误", "请输入股票代码或名称")
            return

        # 设置参数
        self.processor.user_input = stock_input
        self.processor.period = self.combo_period.currentText()
        self.processor.interval = self.combo_interval.currentText()

        # 禁用按钮并显示进度条
        self.btn_analyze.setEnabled(False)
        self.progress_bar.setVisible(True)
        self.progress_bar.setRange(0, 0)  # 不确定进度模式

        # 映射股票代码
        symbol, name = self.processor.map_stock_code(stock_input)
        self.stock_name = name  # 保存股票名称

        # 获取股票ID
        self.current_stock_id = self.cache_manager.get_stock_id(
            stock_input, symbol, name
        )

        # 异步加载缓存数据
        self.cache_manager.load_stock_data_async(
            self.current_stock_id,
            self.on_stock_data_loaded
        )
        self.status_bar.showMessage(f"正在加载 {stock_input} 的缓存数据...")

    def on_stock_data_loaded(self, cached_data):
        """当缓存数据加载完成时的回调"""
        if not self.alive:
            return

        if cached_data is not None and not cached_data.empty:
            logger.info("使用缓存数据")
            self.current_data = cached_data
            self.safe_call(self.on_data_fetched, cached_data)

            # 异步加载关键点
            self.cache_manager.load_key_points_async(
                self.current_stock_id,
                self.on_key_points_loaded
            )
            self.status_bar.showMessage("正在加载关键点...")
        else:
            # 没有缓存数据，启动处理线程获取网络数据
            self.status_bar.showMessage("缓存中没有数据，开始从网络获取...")
            self.processor.start()

    def on_key_points_loaded(self, cached_key_points):
        """当关键点加载完成时的回调"""
        if not self.alive:
            return

        if cached_key_points:
            logger.info("使用缓存的关键点")
            self.current_key_points = cached_key_points
            # 模拟处理完成信号
            self.safe_call(self.on_processing_finished, cached_key_points, {})
            key_points = self.format_key_points(cached_key_points)
            self.safe_call(self.on_key_points_ready, key_points)

            # 更新UI状态
            self.btn_analyze.setEnabled(True)
            self.progress_bar.setVisible(False)

            # 更新图表标题
            self.lbl_original_title.setText(f"{self.stock_name} - 原始K线图")
            self.lbl_chan_title.setText(f"{self.stock_name} - 缠论分析结果")
            self.status_bar.showMessage(f"分析完成 | {self.stock_name} 数据已缓存")

            # 更新自选股按钮状态
            self.update_watchlist_button()
        else:
            # 没有关键点缓存，启动处理线程
            self.status_bar.showMessage("缓存中没有关键点数据，开始处理K线...")
            self.processor.start()

    def format_key_points(self, key_points):
        """格式化关键点信息"""
        if not key_points:
            return "没有有效关键点"
        return "\n".join([f"{kp['Date'].strftime('%Y-%m-%d')} {kp['point_type']} {kp['price']}" for kp in key_points])

    def on_period_changed(self):
        """当分析周期改变时，重新绘制图表"""
        self.safe_call(self._on_period_changed)

    def _on_period_changed(self):
        """实际处理周期变化"""
        if self.current_data is None or self.current_data.empty:
            return

        # 获取当前选中的周期
        period = self.combo_period.currentText()

        # 根据周期计算需要显示的数据量
        days_map = {
            "1月": 30,
            "3月": 90,
            "6月": 180,
            "1年": 365,
            "2年": 730,
            "5年": 1825
        }

        days = days_map.get(period, 365)

        # 截取最近days天的数据
        end_date = self.current_data.index.max()
        start_date = end_date - timedelta(days=days)

        # 确保数据按日期排序
        data = self.current_data.sort_index(ascending=True)

        # 筛选指定时间段的数据
        period_data = data[(data.index >= start_date) & (data.index <= end_date)]

        if not period_data.empty:
            # 重新绘制原始图表
            self.plot_original_chart(period_data)

            # 重新绘制缠论分析图
            if self.current_key_points:
                # 筛选关键点
                key_points = [kp for kp in self.current_key_points
                              if start_date <= kp['Date'] <= end_date]
                self.plot_chan_chart(key_points)

            # 更新状态栏
            self.status_bar.showMessage(f"已切换到 {period} 周期 | 显示 {len(period_data)} 根K线")
        else:
            self.status_bar.showMessage(f"没有找到 {period} 周期的数据")

    def refresh_current(self):
        """刷新当前股票数据"""
        if self.current_stock_id is None:
            return

        # 获取当前股票信息
        self.cache_manager.get_recent_stocks_async(
            1,
            self.on_refresh_data_loaded
        )
        self.status_bar.showMessage("正在刷新股票数据...")

    def on_refresh_data_loaded(self, recent_stocks):
        """当刷新数据加载完成时的回调"""
        if not self.alive:
            return

        if recent_stocks:
            user_input = recent_stocks[0][1]
            self.entry_stock.setText(user_input)
            self.start_analysis()

    def on_data_fetched(self, data):
        """当数据获取完成时"""
        # 保存完整数据
        self.current_data = data

        # 根据当前周期筛选数据
        self.safe_call(self.on_period_changed)

        # 更新状态
        self.status_bar.showMessage(f"已获取 {len(data)} 根K线数据，开始处理...")

    def on_processing_started(self):
        """当处理开始时"""
        self.status_bar.showMessage("正在处理K线数据...")

    def on_processing_finished(self, key_points, state_data):
        """当处理完成时"""
        if not self.alive:
            return

        # 保存完整关键点
        self.current_key_points = key_points

        # 保存缠论状态数据
        self.chan_state_data = state_data

        # 更新缠论状态显示
        self.safe_call(self.update_chan_state, state_data)

        # 根据当前周期筛选关键点
        period = self.combo_period.currentText()
        days_map = {
            "1月": 30,
            "3月": 90,
            "6月": 180,
            "1年": 365,
            "2年": 730,
            "5年": 1825
        }

        days = days_map.get(period, 365)
        end_date = max(kp['Date'] for kp in key_points) if key_points else datetime.now()
        start_date = end_date - timedelta(days=days)

        # 筛选关键点
        period_key_points = [kp for kp in key_points
                             if start_date <= kp['Date'] <= end_date]

        # 绘制缠论分析图
        self.safe_call(self.plot_chan_chart, period_key_points)

        # 更新状态
        self.status_bar.showMessage("K线处理完成")

    def on_key_points_ready(self, key_points):
        """当关键点准备好时 - 增加错误处理"""
        self.safe_call(self._on_key_points_ready, key_points)

    def _on_key_points_ready(self, key_points):
        """实际处理关键点"""
        try:
            # 检查关键点数据是否有效，以及文本框控件是否已初始化
            if key_points and hasattr(self, 'txt_key_points') and self.txt_key_points is not None:
                self.txt_key_points.setText(key_points)
            else:
                logger.warning("关键点数据无效或文本框未初始化")
        except Exception as e:
            logger.error(f"设置关键点时出错: {str(e)}")
            # 尝试安全的备选方案
            try:
                if hasattr(self, 'txt_key_points') and self.txt_key_points is not None:
                    self.txt_key_points.clear()
                    self.txt_key_points.append("无法显示关键点数据")
            except:
                pass

    def on_cache_updated(self, stock_id, stock_name, state_data):
        """当缓存更新时 - 更新图表标题和缠论状态"""
        if not self.alive:
            return

        # 设置当前股票ID
        self.current_stock_id = stock_id

        # 保存缠论状态数据
        self.chan_state_data = state_data

        # 更新缠论状态显示
        self.safe_call(self.update_chan_state, state_data)

        # 更新图表标题
        self.lbl_original_title.setText(f"{stock_name} - 原始K线图")
        self.lbl_chan_title.setText(f"{stock_name} - 缠论分析结果")

        # 恢复界面状态
        self.btn_analyze.setEnabled(True)
        self.progress_bar.setVisible(False)
        self.status_bar.showMessage(f"分析完成 | {stock_name} 数据已缓存")

        # 更新历史记录
        self.load_history_async()

        # 更新自选股按钮状态
        self.update_watchlist_button()

    def on_error_occurred(self, error_msg):
        """当发生错误时"""
        QMessageBox.critical(self, "错误", error_msg)

        # 恢复界面状态
        self.btn_analyze.setEnabled(True)
        self.progress_bar.setVisible(False)
        self.status_bar.showMessage("就绪")

    def plot_original_chart(self, data):
        """绘制原始K线图 - 动态调整尺寸并限制数据点"""
        if data is None or data.empty:
            return

        try:
            # 限制最大数据点数
            MAX_POINTS = 500
            if len(data) > MAX_POINTS:
                logger.warning(f"数据点过多 ({len(data)} > {MAX_POINTS})，进行降采样")
                # 等间隔采样
                step = max(1, len(data) // MAX_POINTS)
                data = data.iloc[::step]

            # 清除并重新创建图表
            self.canvas_original.fig.clear()
            ax = self.canvas_original.fig.add_subplot(111)

            # 获取画布的实际尺寸（像素）
            canvas_width = max(100, self.canvas_original.width())
            canvas_height = max(100, self.canvas_original.height())

            # 动态设置图表尺寸（英寸）
            dpi = self.canvas_original.fig.dpi
            fig_width = canvas_width / dpi
            fig_height = canvas_height / dpi
            self.canvas_original.fig.set_size_inches(fig_width, fig_height)

            # 准备日期数据
            dates = data.index.to_pydatetime()

            # 创建日期索引数组
            x = np.arange(len(dates))

            # 设置每个蜡烛的宽度（基于数据量动态调整）
            candle_width = max(0.1, min(0.8, 10 / len(dates)))  # 宽度在0.1-0.8之间

            # 绘制蜡烛图
            for i in range(len(data)):
                row = data.iloc[i]
                date = dates[i]

                # 确定颜色
                color = 'g' if row['Close'] >= row['Open'] else 'r'

                # 绘制影线
                ax.plot([i, i], [row['Low'], row['High']], color=color, linewidth=1.5)

                # 绘制实体
                ax.bar(i, height=abs(row['Close'] - row['Open']),
                       bottom=min(row['Open'], row['Close']),
                       width=candle_width, color=color)

            # 设置标签 - 使用用户设置的图表字体大小
            font_size = self.chart_font_size
            ax.set_xlabel('日期', fontsize=font_size)
            ax.set_ylabel('价格', fontsize=font_size)
            ax.grid(True, linestyle='--', alpha=0.7)

            # 设置X轴刻度
            ax.set_xticks(x)

            # 仅显示部分日期标签以避免重叠
            step = max(1, len(dates) // 10)  # 大约显示10个标签
            visible_ticks = [i for i in range(0, len(dates), step)]
            ax.set_xticks(visible_ticks)

            # 格式化日期标签
            ax.set_xticklabels([dates[i].strftime('%Y-%m-%d') for i in visible_ticks],
                               rotation=45, ha='right', fontsize=font_size * 0.8)

            # Y轴刻度标签字体
            ax.tick_params(axis='y', which='major', labelsize=font_size * 0.8)

            # 自动调整Y轴范围
            y_min = data[['Low']].min().values[0] * 0.98
            y_max = data[['High']].max().values[0] * 1.02
            ax.set_ylim(y_min, y_max)

            # 调整布局，使图表填满空间
            self.canvas_original.fig.tight_layout(pad=2.0)  # 减少内边距
            self.canvas_original.draw()
        except Exception as e:
            logger.error(f"绘制原始K线图时出错: {str(e)}")
            try:
                self.canvas_original.fig.clear()
                ax = self.canvas_original.fig.add_subplot(111)
                ax.text(0.5, 0.5, '绘制图表时出错',
                        horizontalalignment='center', verticalalignment='center',
                        fontsize=12, transform=ax.transAxes)
                self.canvas_original.draw()
            except:
                logger.error("无法显示图表错误信息")

    def plot_chan_chart(self, key_points):
        """绘制缠论分析图 - 动态调整尺寸并限制数据点"""
        if key_points is None:
            return

        try:
            # 限制最大关键点数
            MAX_POINTS = 200
            if len(key_points) > MAX_POINTS:
                logger.warning(f"关键点过多 ({len(key_points)} > {MAX_POINTS})，进行降采样")
                # 等间隔采样
                step = max(1, len(key_points) // MAX_POINTS)
                key_points = key_points[::step]

            self.canvas_chan.fig.clear()
            ax = self.canvas_chan.fig.add_subplot(111)

            # 获取画布的实际尺寸（像素）
            canvas_width = max(100, self.canvas_chan.width())
            canvas_height = max(100, self.canvas_chan.height())

            # 动态设置图表尺寸（英寸）
            dpi = self.canvas_chan.fig.dpi
            fig_width = canvas_width / dpi
            fig_height = canvas_height / dpi
            self.canvas_chan.fig.set_size_inches(fig_width, fig_height)

            if not key_points:
                ax.text(0.5, 0.5, '没有关键点数据',
                        horizontalalignment='center', verticalalignment='center',
                        fontsize=12, transform=ax.transAxes)
                self.canvas_chan.draw()
                return

            # 提取笔的顶点和底点
            tops = [kp for kp in key_points if '笔顶' in kp['point_type']]
            bottoms = [kp for kp in key_points if '笔底' in kp['point_type']]

            # 按日期排序
            tops = sorted(tops, key=lambda x: x['Date'])
            bottoms = sorted(bottoms, key=lambda x: x['Date'])

            # 连接笔的顶点和底点形成线段
            pen_lines = []
            current_top = None
            current_bottom = None

            # 找到第一个有效笔
            if tops and bottoms:
                if tops[0]['Date'] < bottoms[0]['Date']:
                    # 第一个是顶分型（向下笔）
                    current_top = tops[0]
                    tops = tops[1:]
                else:
                    # 第一个是底分型（向上笔）
                    current_bottom = bottoms[0]
                    bottoms = bottoms[1:]

            while tops and bottoms:
                if current_top:
                    # 寻找下一个底分型（完成向下笔）
                    next_bottom = None
                    for bottom in bottoms:
                        if bottom['Date'] > current_top['Date']:
                            next_bottom = bottom
                            break

                    if next_bottom:
                        pen_lines.append((current_top, next_bottom))
                        current_bottom = next_bottom
                        bottoms = [b for b in bottoms if b['Date'] > next_bottom['Date']]
                        current_top = None

                if current_bottom:
                    # 寻找下一个顶分型（完成向上笔）
                    next_top = None
                    for top in tops:
                        if top['Date'] > current_bottom['Date']:
                            next_top = top
                            break

                    if next_top:
                        pen_lines.append((current_bottom, next_top))
                        current_top = next_top
                        tops = [t for t in tops if t['Date'] > next_top['Date']]
                        current_bottom = None

            # 绘制笔的线段
            line_width = max(1, min(3, fig_width / 100))  # 线宽在1-3之间动态调整
            for start, end in pen_lines:
                # 向下笔（红色）
                if '笔顶' in start['point_type']:
                    ax.plot([start['Date'], end['Date']],
                            [start['price'], end['price']],
                            'r-', linewidth=line_width)
                # 向上笔（绿色）
                else:
                    ax.plot([start['Date'], end['Date']],
                            [start['price'], end['price']],
                            'g-', linewidth=line_width)

            # 绘制分型点
            marker_size = max(8, min(16, fig_width / 20))  # 标记尺寸在8-16之间动态调整
            for k in key_points:
                if '笔顶' in k['point_type']:
                    # 顶分型（红色倒三角）
                    ax.plot(k['Date'], k['price'],
                            'r^', markersize=marker_size,
                            markeredgecolor='black', markeredgewidth=0.5)

                    # 添加标签
                    ax.text(k['Date'], k['price'] * 1.01, '笔顶',
                            fontsize=10, bbox=dict(facecolor='white', alpha=0.7, edgecolor='none'),
                            ha='center', va='bottom')

                elif '笔底' in k['point_type']:
                    # 底分型（绿色正三角）
                    ax.plot(k['Date'], k['price'],
                            'gv', markersize=marker_size,
                            markeredgecolor='black', markeredgewidth=0.5)

                    # 添加标签
                    ax.text(k['Date'], k['price'] * 0.99, '笔底',
                            fontsize=10, bbox=dict(facecolor='white', alpha=0.7, edgecolor='none'),
                            ha='center', va='top')

            # 设置标签 - 使用用户设置的图表字体大小
            font_size = self.chart_font_size
            ax.set_xlabel('日期', fontsize=font_size)
            ax.set_ylabel('价格', fontsize=font_size)
            ax.grid(True, linestyle='--', alpha=0.7)

            # 刻度标签字体
            ax.tick_params(axis='both', which='major', labelsize=font_size * 0.8)

            # 自动调整图表范围
            prices = [kp['price'] for kp in key_points]
            y_min, y_max = min(prices), max(prices)
            price_range = y_max - y_min
            ax.set_ylim(y_min - price_range * 0.1, y_max + price_range * 0.1)

            # 格式化日期轴
            ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d'))
            self.canvas_chan.fig.autofmt_xdate()

            # 调整布局，使图表填满空间
            self.canvas_chan.fig.tight_layout(pad=2.0)  # 减少内边距

            self.canvas_chan.draw()
        except Exception as e:
            logger.error(f"绘制缠论分析图时出错: {str(e)}")
            try:
                self.canvas_chan.fig.clear()
                ax = self.canvas_chan.fig.add_subplot(111)
                ax.text(0.5, 0.5, '绘制图表时出错',
                        horizontalalignment='center', verticalalignment='center',
                        fontsize=12, transform=ax.transAxes)
                self.canvas_chan.draw()
            except:
                logger.error("无法显示图表错误信息")

    def focusOutEvent(self, event):
        """当窗口失去焦点时隐藏搜索建议"""
        if not self.entry_stock.hasFocus() and not self.search_suggestions.hasFocus():
            self.search_suggestions.setVisible(False)
        super().focusOutEvent(event)

    def closeEvent(self, event):
        """窗口关闭时清理资源"""
        logger.info("清理应用程序资源")
        self.alive = False  # 设置应用关闭标志

        # 清理处理器线程
        try:
            if hasattr(self, 'processor') and self.processor:
                if self.processor.isRunning():
                    logger.info("正在停止处理器线程...")
                    self.processor.terminate()
                    self.processor.wait(3000)  # 等待更长时间
                    if self.processor.isRunning():
                        logger.warning("处理器线程未正常停止，强制终止")
                        self.processor.quit()
        except Exception as e:
            logger.error(f"清理处理器线程时出错: {str(e)}")

        # 清理缓存管理器
        try:
            if hasattr(self, 'cache_manager') and self.cache_manager:
                logger.info("正在关闭缓存管理器...")
                # 确保所有线程任务完成
                self.cache_manager.executor.shutdown(wait=False, cancel_futures=True)
                # 关闭数据库连接
                if hasattr(self.cache_manager, '_thread_local'):
                    if hasattr(self.cache_manager._thread_local, "conn") and self.cache_manager._thread_local.conn:
                        self.cache_manager._thread_local.conn.close()
                        self.cache_manager._thread_local.conn = None
        except Exception as e:
            logger.error(f"清理缓存管理器时出错: {str(e)}")

        # 清理图表资源
        try:
            logger.info("正在清理图表资源...")
            plt.close('all')  # 关闭所有Matplotlib图形
            if hasattr(self, 'canvas_original') and self.canvas_original:
                self.canvas_original.fig.clear()
                self.canvas_original.deleteLater()
            if hasattr(self, 'canvas_chan') and self.canvas_chan:
                self.canvas_chan.fig.clear()
                self.canvas_chan.deleteLater()
        except Exception as e:
            logger.error(f"清理图表资源时出错: {str(e)}")

        # 清除大型数据集
        self.current_data = None
        self.current_key_points = None
        self.chan_state_data = None

        # 移除中央组件
        logger.info("正在移除中央组件...")
        self.takeCentralWidget()  # 移除中央组件，避免内存泄漏
        if self.centralWidget():
            self.centralWidget().deleteLater()

        # 清理所有子控件
        logger.info("正在清理子控件...")
        for child in self.findChildren(QWidget):
            try:
                child.deleteLater()
            except:
                pass

        # 强制垃圾回收
        logger.info("执行垃圾回收...")
        gc.collect()
        gc.collect()

        # 接受关闭事件
        event.accept()
        logger.info("应用程序已关闭")

        # 强制退出进程 - 作为最后手段
        os._exit(0)  # 使用强制退出避免 Qt 清理问题

    def event(self, event):
        """处理自定义异常事件"""
        # 确保我们处理的是自定义异常事件类型
        if hasattr(QExceptionEvent, 'EVENT_TYPE') and event.type() == QExceptionEvent.EVENT_TYPE:
            # 安全地获取事件属性
            message = getattr(event, 'message', '未知错误')
            traceback = getattr(event, 'traceback', '')

            QMessageBox.critical(self, "线程异常",
                                 f"线程发生未处理异常:\n\n{message}\n\n堆栈跟踪:\n{traceback}")
            return True
        return super().event(event)